﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Sql;
using System.Linq;
using System.Text;
using System.IO;
using Luna;
using Luna.Utils;
using System.Text.RegularExpressions;
using VulnDBExplorer.Services;
using System.Data.SqlClient;

namespace VulnDB.Action.DataCollectors.Services.CodeAge {
	
	class SourceProfileStorage : ISourceProfileStorage, IDisposable {
		private static readonly Regex IndexLinePattern = new Regex(@"(.+?):(\d+) (\d+)", RegexOptions.Multiline);
		private static readonly Regex ProfileEntryPattern = new Regex(@"(\d+(\.\d+)*),(.+?),(\d+-\d+-\d+),(\d+): (.+?)$", RegexOptions.Multiline);

		private Stream mainStorage;
		private Stream indexStorage;
		private StreamWriter mainStorageWriter;
		private StreamWriter indexStorageWriter;
		private string dataFile, indexFile;

		private Dictionary<string, Tuple<long, int>> buffers;

		public SourceProfileStorage() {
		}

		public SourceProfileStorage(string dataFile, string indexFile) {
			this.DataFile = dataFile;
			this.IndexFile = indexFile;
		}

		public ICodebase Codebase { get; set; }

		public string DataFile {
			get { return dataFile; }
			set {
				if (dataFile == null) {
					dataFile = value;
					mainStorage = new FileStream(dataFile, FileMode.OpenOrCreate);
					mainStorageWriter = new StreamWriter(mainStorage);
				}
			}
		}

		public string IndexFile {
			get { return indexFile; }
			set {
				if (indexFile == null) {
					indexFile = value;
					indexStorage = new FileStream(indexFile, FileMode.OpenOrCreate);

					indexStorageWriter = new StreamWriter(indexStorage);
					buffers = new Dictionary<string, Tuple<long, int>>();
					//TODO load buffer from index file.
					using (var f = new StreamReaderWrapper(indexStorage)) {
						string lines = f.ReadToEnd();
						Match m = IndexLinePattern.Match(lines);
						while (m.Success) {
							buffers[m.Groups[1].Value] = new Tuple<long, int>(long.Parse(m.Groups[2].Value), m.Groups[3].Value.toInt());
							m = m.NextMatch();
						}
					}
				}
			}
		}

		public bool IsDisposed { get; private set; }

		public bool hasProfile(string filename, string activefilename = null) {
			return buffers.ContainsKey(filename);
		}

		public ISourceFileProfile loadProfile(string filename) {
			SourceFileProfile profile = null;
			if (buffers.ContainsKey(filename)) {
				Tuple<long, int> location = buffers[filename];
				byte[] buf = new byte[location.Item2];
				mainStorage.Position = location.Item1;
				int len = mainStorage.Read(buf, 0, location.Item2);
				string input = System.Text.UTF8Encoding.UTF8.GetString(buf);

				profile = new SourceFileProfile(filename);
				Match m = ProfileEntryPattern.Match(input);
				while (m.Success) {
					SourceFileRevision rev = new SourceFileRevision(m.Groups[1].Value,
						m.Groups[3].Value, DateTime.Parse(m.Groups[4].Value), null);
					rev.Count = m.Groups[5].Value.toInt();
					foreach (var s in m.Groups[6].Value.Split(' ')) {
						int idx = s.IndexOf('-');
						rev.Lines.Add(new FileCompareResult.FileChunk() 
							{ Begin = s.Substring(0, idx).toInt(), End = s.Substring(idx + 1).toInt() });
					}
					profile.addRevision(rev);
					m = m.NextMatch();
				}
			}
			return profile;
		}

		/// <summary>
		/// Write a source profile to disk, throw exception if there exists a same name profile.
		/// </summary>
		/// <param name="file"></param>
		public void writeProfile(ISourceFileProfile profile) {
			if (!buffers.ContainsKey(profile.Filename)) {
				mainStorage.Position = mainStorage.Length; // move to end of file
				long start = mainStorage.Position;
				// write data to main storage
				mainStorageWriter.WriteLine(profile.Filename);
				foreach (var item in profile)
					mainStorageWriter.WriteLine(item.serializeProfiles());
				mainStorageWriter.Flush();
				int len = (int)(mainStorage.Length - start);
				mainStorageWriter.WriteLine("------");
				mainStorageWriter.Flush();

				// then write the indecies
				buffers.Add(profile.Filename, new Tuple<long,int>(start, len));
				indexStorageWriter.WriteLine("{0}:{1} {2}", profile.Filename, start, len);
				indexStorageWriter.Flush();
			}
		}

		private void dispose(bool disposing) {
			if (disposing) {
				indexStorageWriter.Close();
				mainStorageWriter.Close();
			}
		}

		public void Dispose() {
			if (!IsDisposed) {
				dispose(true);
				IsDisposed = true;
			}
		}
	}

	class SqlSourceProfileStorage : ISourceProfileStorage {

		private static readonly string QueryCheckProfile = "checkProfileExist '{0}', '{1}', '{2}' ";
		private static readonly string QuerySaveProfile = "saveProfile '{0}', '{1}', '{2}', '{3}', '{4}', '{5:yyyy-MM-dd}', {6}";
		private static readonly string QuerySaveLOC = "insert into LOC values({0}, {1}, {2});";
		//private SqlConnection db,db2;

		public SqlSourceProfileStorage(ICodebase codebase, string connectionStr) {
			this.Codebase = codebase;
			this.ConnectionStr = connectionStr;
			//db = new SqlConnection(connectionStr);
			//db.Open();
			//db2 = new SqlConnection(connectionStr);
			//db2.Open();
		}

		public SqlSourceProfileStorage(ICodebase codebase, string server, string database) {
			this.Codebase = codebase;
			this.ConnectionStr = string.Format("Integrated Security=SSPI;server={0};database={1};trusted_connection=yes", server, database);
			//db = new SqlConnection(ConnectionStr);
			//db.Open();
			//db2 = new SqlConnection(ConnectionStr);
			//db2.Open();
		}

		public string ConnectionStr { get; protected set; }

		public ICodebase Codebase { get; set; }

		public ISourceFileProfile loadProfile(string filename) {
			throw new NotImplementedException();
		}

		public void writeProfile(ISourceFileProfile profile) {
			using (var db = new SqlConnection(ConnectionStr)) {
				db.Open();
				using (var trans = db.BeginTransaction()) {
					foreach (var item in profile) {
						string cmdText = QuerySaveProfile.format(profile.Filename, profile.ActiveFilename, Codebase.Name,
							item.Author, item.Revision, item.Age, item.Count);
						decimal itemId = 0;
						using (var cmd = new SqlCommand(cmdText, db, trans)) {
							itemId = (decimal)cmd.ExecuteScalar();
						}
						if (itemId > 0) {
							StringBuilder b = new StringBuilder();
							foreach (var loc in item.Lines) {
								b.AppendLine(QuerySaveLOC.format(itemId, loc.Begin, loc.End));
							}
							using (var loccmd = new SqlCommand(b.ToString(), db, trans))
								loccmd.ExecuteNonQuery();
						}
					}
					trans.Commit();
				}
				db.Close();
			}
			
		}

		public bool hasProfile(string filename, string activefilename = null) {
			bool existed = false;
			if (string.IsNullOrWhiteSpace(activefilename))
				activefilename = filename;
			using (var db = new SqlConnection(ConnectionStr)) {
				db.Open();
				using (var cmd = new SqlCommand(QueryCheckProfile.format(Codebase.Name, filename, activefilename), db))
					existed = (bool)cmd.ExecuteScalar();
				db.Close();
			}
			return existed;
		}
	}
}
